// Copyright 2012 dmitry.a.novikov@gmail.com
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "consistenz.h"
#include "dbobjectimpl.h"
#include "objectimpl.h"
#include "filer.h"
#include "dbobject.h"
#include "domain.h"
#include "domainimpl.h"

namespace CnsZ {

  DEFINE_CNSZ_CLASS(DObject, Object, NULL_FACTORY);

  DEFINE_CNSZ_CLASS(ObjectIterator, Object, NULL_FACTORY);

  DEFINE_CNSZ_CLASS(Domain, DDictionary, DBOBJECT_FACTORY);


  class HandleImpl : public Handle {
    P<Handle> prnt;
    P<Object> obj;
  protected:
    HandleImpl() {}
    ~HandleImpl() {
      attach(0); // release DObject
    }
  public:

    DECLARE_CNSZ_DBCLASS(HandleImpl);

    P<Domain> domain() const {
      P<Handle> prnt = this->prnt;
      P<Domain> dom;
      for(;;) {
        P<DObject> dobj = prnt->openR();
        if(!dobj.get()) {
          break;
        }
        dom = castTo<Domain>(dobj);
        if(dom.get()) {
          break;
        }
        prnt = prnt->parent();
      }
      return dom;
    }

    P<Handle> parent() const {
      return prnt;
    }
    
    void setParent(Handle* prnt) {
      this->prnt = prnt;
    }

    P<Object> object() const {
      return obj;
    }

    P<DObject> openR() {
      return castTo<DObject>(object().get());
    }

    P<DObject> openW() {
      return castTo<DObject>(object().get());
    }

    P<Object> attach(Object* object) {
      P<Object> res = obj;
      if(obj.get()) {
        P<DObject> dbobj = castTo<DObject>(obj);
        if(dbobj.get()) {
          dbobj->willBeUnappended();
        }
      }
      this->obj = object;
      if(object) {
        P<DObject> dbobj = castTo<DObject>(object);
        if(dbobj.get()) {
          dbobj->appended();
        }
      }
      return res;
    }
    Filer* undoFiler() {
      return 0;
    }
  };

  DEFINE_CNSZ_CLASS(HandleImpl, Handle, OBJECTIMPL_FACTORY);

  DEFINE_CNSZ_CLASS(Handle, Object, IMPL_FACTORY);


  DObject::DObject(DObjectImpl* imp) : impl(imp) {}

  DObject::DObject() {
    impl = new DObjectImpl;
  }

  DObject::~DObject() {
    delete impl;
  }

  void DObject::addRef() const {
    ++impl->refCounter;
  }

  void DObject::release() const {
    --impl->refCounter;
    if(impl->handle) {
      if(impl->refCounter==1) {
        // close DB object
      }
    }
    else {
      if(impl->refCounter==0) {
        delete this;
      }
    }
  }
  
  int DObject::numRefs() const {
    return impl->refCounter;
  }

  P<Handle> DObject::handle() const {
    return impl->handle;
  }

  P<Domain> DObject::domain() const {
    P<Handle> h = handle();
    if(h.get()) {
      return h->domain();
    }
    return P<Domain>();
  }

  void DObject::read(Filer& filer) {
    fireWillBeModified();
    P<Handle> handle = filer.rdHandle();
    if(!filer.isKindOf(CopyFiler::classInfo())) {
      impl->handle = handle;
    }
  }

  void DObject::write(Filer& filer) const {
    filer.wrHandle(handle());
  }

  String DObject::toString() const {
    return Object::toString();
//    std::wostringstream s;
//    s << '<' << isA()->name() << ':' << handle()->toString() << '>';
//    return String(s.str().data());
  }

  void DObject::fireWillBeModified(int ) {
    if(!getbit(impl->flags, DObjectImpl::Modified)) {
      setbit(impl->flags, DObjectImpl::Modified, true);
    }
  }
  
  Filer* DObject::undoFiler() {
    if(impl->handle) {
      return static_cast<HandleImpl*>(impl->handle)->undoFiler();
    }
    return 0;
  }

  void DObject::appended() {
  }

  void DObject::willBeUnappended() {
    impl->handle = 0;
  }

  Domain::Domain(DomainImpl* impl) : DDictionary(impl) {}

  Domain::~Domain() {}

  void Domain::read(Filer& filer) {
    DDictionary::read(filer);
  }

  void Domain::write(Filer& filer) const {
    DDictionary::write(filer);
  }

  P<Handle> Domain::attach(DObject* object, Handle* prnt) {
    if(object) {
      if(!prnt) {
        prnt = handle();
      }
      P<HandleImpl> himp;
      P<Handle> h = object->handle();
      if(h.get()) {
        himp = castTo<HandleImpl>(h);
        if(himp.get() && himp->domain()==this) {
        }
        else { // redirect handle
          himp = HandleImpl::New();
        }
      }
      if(!himp.get()) {
        himp = HandleImpl::New();
      }
      himp->setParent(prnt);
      object->appended();
      return h;
    }
    throw NullPointer();
  }

}//namespace CnsZ
